<?php

namespace html\core;

Abstract class abstractTagParser
{

	private $_objectArray;
	private $_htmlSourceCode;
	private $_S;
	private $_A;
	private $_T;
	private $_tagClass;

	private function setWhitespace($rx)
	{
		$this->_S = $rx;
	}

	private function getWhitespace()
	{
		return $this->_S;
	}

	private function setAttribute($rx)
	{
		$this->_A = $rx;
	}

	private function getAttribute()
	{
		return $this->_A;
	}

	private function setTagname($rx)
	{
		$this->_T = $rx;
	}

	private function getTagname()
	{
		return $this->_T;
	}

	public function setTagClass($className)
	{
		$this->_tagClass = $className;
	}

	public function getTagClass()
	{
		return $this->_tagClass;
	}

	private function setHtmlSource($code)
	{
		$this->_htmlSourceCode = $code;
	}

	public function getHtmlSource()
	{
		return $this->_htmlSourceCode;
	}

	public function getTags()
	{
		return $this->_objectArray;
	}

	public function getSmallTags()
	{
		if( $this->_objectArray ) foreach( $this->_objectArray as $id => $tag )
			{
				$smallTag = clone $tag;
				$smallTag->stripDown();
				$tagArray[$id] = $smallTag;
			}
		return $tagArray;
	}

	public function addTag($id, $tag)
	{
		$this->_objectArray[$id] = $tag;
	}

	public function getCount()
	{
		return count($this->_objectArray);
	}

	/**
	 * This function initializes several static variables for their fresh use within the parser/tokenizer.
	 */
	private function initialize()
	{
		$this->_objectArray = array();
		$this->setWhitespace(" \t\r\n\f"); //Valid Whitespace description
		$this->setAttribute("[a-zA-Z_:][a-zA-Z0-9_:.-]*"); //Valid Attribute description
		$this->setTagname("[a-zA-Z]{1}[-a-zA-Z0-9:_]*"); //Valid Tagname description
	}

	/**
	 * This is the master create function. This is the place, where an object(Collection) gets born from plain text/html
	 * @param string $string The string containing any valid text/html
	 * @return array objectCollection
	 */
	function create($string)
	{
		$this->setHtmlSource($string);
		$this->initialize();
		$S = $this->getWhitespace();
		$A = $this->getAttribute();
		$T = $this->getTagname();

		//<([ ]*?[a-zA-Z]{1}[-a-zA-Z:_]*[ ]*?)((?:(?:(?:[a-z][a-z0-9]*[ ]*?=[ ]*?('|\")((?:\\\3|[^\3])*?)(\3)|(?:[a-zA-Z_:][a-zA-Z0-9_:.-]*))[ \t\r\n\f]*)*))(?:>|/[ ]*>)
		//<([$S]*?[/]?$T[$S]*?)((?:(?:(?:$A[$S]*?=[$S]*?('|\")(?:(?:\\\3|[^\3])*?)(\3)|(?:$A))[$S]*)*))(?:>|/[$S]*>)
		//<([ \t\r\n\f]*?[/]?[a-zA-Z]{1}[-a-zA-Z0-9:_]*[ \t\r\n\f]*?)((?:(?:(?:[a-zA-Z_:][a-zA-Z0-9_:.-]*[ \t\r\n\f]*?=[ \t\r\n\f]*?('|\")(?:(?:\\\3|[^\3])*?)(\3)|(?:[a-zA-Z_:][a-zA-Z0-9_:.-]*))[ \t\r\n\f]*)*))(?:>|/[ \t\r\n\f]*>)
		$rx = "<"; //Look for an opener
		$rx.= "([".$S."]*?[/]?".$T."[".$S."]*?)"; //Find a valid start or ending tag and store it
		$rx.= "("; //Start recording the attributes
		$rx.= "(?:"; //Take all attributes
		$rx.= "(?:"; //Loop through attributes
		$rx.= "(?:"; //There are fullfeatured attributes and singlekey attribute decide which one to take
		$rx.= "".$A."[".$S."]*?=[".$S."]*?('|\")(?:(?:\\\\\\3|[^\\3])*?)(\\3)"; //Find fullfeatured attributes
		$rx.= "|(?:".$A."?(?:=[^ >]*)?)"; //Or be fine with a valid singlekey attribute/unquoted attribute
		$rx.= ")"; //Decision for an attribute finished
		$rx.= "[".$S."]*"; //Next attribute might be spaced by valid space characters
		$rx.= ")*"; //Finish attribute loop
		$rx.= ")"; //Take as long attributes as there is not the ending sign >
		$rx.= ")"; //And store them in a string
		$rx.= "(?:>|(/)[".$S."]*>)"; //The end is reached if there is a closing sign found >

		preg_match_all("!".$rx."!sm", $string, $matched_tags, PREG_OFFSET_CAPTURE);
		$this->tokenizer($matched_tags);

		return $this;
	}

	/**
	 * This function splits an html command into its parts for further investigation
	 * @param array $matchArray Return from preg_match_all in create
	 */
	private function tokenizer($matchArray)
	{
		unset($matchArray[3]);
		unset($matchArray[4]);
		$ELEMENTS = 0;
		$TAG = 0;
		$TAGNAME = 1;
		$ATTRIBUTES = 2;
		$SINGLETAG = 5;

		$VALUE = 0;
		$OFFSET = 1;

		$tagElement = $this->getTagClass();

		if( $matchArray[$ELEMENTS] ) foreach( $matchArray[$ELEMENTS] as $ID => $foo )
			{
				$tag = new $tagElement();
				$tag->setId($ID);
				$tag->setOffset($matchArray[$TAG][$ID][$OFFSET]);
				$tag->setLength(strlen($matchArray[$TAG][$ID][$VALUE]));
				$tag->createTag($matchArray[$TAGNAME][$ID][$VALUE], $matchArray[$SINGLETAG][$ID][$VALUE]);
				$this->attribute_tokenizer($tag, $matchArray[$ATTRIBUTES][$ID][0]);
				$this->addTag($ID, $tag);
			}
	}

	/**
	 * This function traces/splits the attributes out of a string containing all attributes in a raw form
	 * @param string $attributeString Is the attribute string given from the tag_tokenizer/create
	 */
	private function attribute_tokenizer($tagObject, $attributeString)
	{
		$S = $this->getWhitespace();
		$A = $this->getAttribute();
		$T = $this->getTagname();

		$ATTRIBUTEPAIR = 0;
		$ATTRIBUTEDELIMITER = 1;

		$rx = "";
		$rx.= "".$A."[".$S."]*?=[".$S."]*?('|\")(?:(?:\\\\\\1|[^\\1])*?)(\\1)"; //Find fullfeatured attributes
		$rx.= "|(?:".$A.")[".$S."]*?=[^ ]*|[a-zA-Z]{1}[a-zA-Z0-9:-]*"; //Or be fine with a valid singlekey attribute
		while( preg_match("!".$rx."!sm", $attributeString, $match) )
		{
			$tagObject->addAttribute($match[$ATTRIBUTEPAIR], $match[$ATTRIBUTEDELIMITER]);
			$pos = strpos($attributeString, $match[$ATTRIBUTEPAIR]);
			$attributeString = substr_replace($attributeString, "", $pos, strlen($match[$ATTRIBUTEPAIR]));
		}
	}

	/**
	 * Determines the level of the stream to assign tag objects to their corresponding depth
	 * @param array $objectArray
	 */
	public function level_tags()
	{
		$objectArray = $this->getTags();
		if( $objectArray ) foreach( $objectArray as $key => $tag )
			{
				if( $tag->getType() == TAG_OPEN )
				{
					$level++;
					$stack[$tag->getTagname()]++;
					$tag->setLevel($level);
				}
				elseif( $tag->getType() == TAG_CLOSE )
				{
					$tag->setLevel($level);
					$level--;
					$stack[$tag->getTagname()]--;
				}
				elseif( $tag->getType() == TAG_SINGLE )
				{
					$level++;
					$tag->setLevel($level);
					$level--;
				}
			}
	}

	public function build_rel_for_tag($tagArray, $tag, $pos)
	{
		$c = count($tagArray);
		for( $x = $pos + 1; $x < $c; $x++ )
		{
			if( $tagArray[$x]->getLevel() - 1 == $tag->getLevel() )
			{
//				echo "Add\n";
//				echo $tag->getTagname()." bekommen ".$tagArray[$x]->getTagname()."\n";
				$tag->addChild($tagArray[$x]);
			}
			if($tagArray[$x]->getLevel() == $tag->getLevel() ){
				if(is_object($tagArray[$x+1]) && $tagArray[$x+1]->getLevel() == $tag->getLevel()){
					$tag->addSibling($tagArray[$x+1]);
				}
				return;
			}
		}
	}

	public function build_relations()
	{
		$objectArray = $this->getTags();
//		$objectArray = $this->getSmallTags();
//		print_r($objectArray);
//		exit;

		if( $objectArray ) foreach( $objectArray as $key => $tag )
			{

				$this->build_rel_for_tag($objectArray, $tag, $key);
//				echo $tag->getTagname()."\n";

//				if( is_object($objectArray[$key + 1]) && $objectArray[$key + 1]->getLevel() > $tag->getLevel() )
//				{
//					$childTag = $objectArray[$key + 1];
////					echo "Add as Child\n";
//					$tag->addChild($childTag);
//				}

				if( $tag->getType() == TAG_OPEN || $tag->getType() == TAG_SINGLE )
				{
					for( $dive = $key + 1; $dive <= count($objectArray) - 1; $dive++ )
					{
						if( $objectArray[$dive]->getType() == TAG_CLOSE &&
										$objectArray[$dive]->getLevel() == $tag->getLevel() )
						{
							$tag->setCloseTag($objectArray[$dive]);
							$objectArray[$dive]->setOpenTag($tag);
							break;
						}
					}

					for( $dive = $key - 1; $dive >= 0; $dive-- )
					{
						if( $objectArray[$dive]->getType() == TAG_OPEN &&
										$objectArray[$dive]->getLevel() == $tag->getLevel() - 1 )
						{
							$tag->setParent($objectArray[$dive]);
							break;
						}
					}
				}
			}
	}

}

?>
